﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;

namespace Mobwiz.SpellChecker.Core.Dictionaries
{
    public class MemoryDictionary : Mobwiz.SpellChecker.Core.ISpellDictionary
    {
        #region Fields

        private char[] wordsContent;
        private char[] codesContent = null;
        private CodeObject[] codesObjects = null;
        //protected int wordsCount;

        private int totalCodeCount = 0;// = binReader.ReadInt32();
        private int totalCodeSize = 0;// = binReader.ReadInt32();
        private int totalWordCount = 0;// = binReader.ReadInt32();
        private int totalWordSize = 0;// = binReader.ReadInt32();

        internal struct CodeObject
        {
            public int CodeStartIndex;
            public int FirstWordStartIndex;
        }

        //private readonly int EditDistanceThreshold = ConfigurationEditDistance.SUG_EDIT_DISTANCE_THRESHOLD;

        #endregion

        public IEnumerable<string> GetWordsByCode(string phonetCode)
        {
            int codeIndex = this.GetCodeIndex(phonetCode);

            if (codeIndex < 0) return new string[] { };

            int startPos, endPos;

            StringBuilder sbWords = new StringBuilder();
            CodeObject obj = codesObjects[codeIndex];
            startPos = obj.FirstWordStartIndex;

            if ((codeIndex + 1) < totalCodeCount)
            {
                endPos = codesObjects[codeIndex + 1].FirstWordStartIndex - 1;
            }
            else
                endPos = totalCodeSize - 1;

            for (int i = startPos; i <= endPos; i++)
                sbWords.Append(wordsContent[i]);

            string[] words = sbWords.ToString().Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
            return words;
        }

        public void LoadDictionary(string dictPath)
        {
            try
            {
                //FileStream fs = new FileStream(wordListFile, FileMode.Open);
                //BinaryFormatter formater = new BinaryFormatter();

                //dictSet = (formater.Deserialize(fs)) as Dictionary<string, List<string>>;

                //fs.Close();
                //fs.Dispose();
                if (!File.Exists(dictPath))
                {
                    throw new FileNotFoundException("Dictionary file not found!");
                }

                BinaryReader binReader =
                    new BinaryReader(File.Open(dictPath, FileMode.Open));

                totalCodeCount = binReader.ReadInt32();
                totalCodeSize = binReader.ReadInt32();
                totalWordCount = binReader.ReadInt32();
                totalWordSize = binReader.ReadInt32();

                codesObjects = new CodeObject[totalCodeCount];

                for (int i = 0; i < totalCodeCount; i++)
                {
                    codesObjects[i].CodeStartIndex = binReader.ReadInt32();
                    codesObjects[i].FirstWordStartIndex = binReader.ReadInt32();
                }

                codesContent = binReader.ReadChars(totalCodeSize);
                wordsContent = binReader.ReadChars(totalWordSize);

                binReader.Close();
            }
            catch (Exception ee)
            {
                throw (ee);
            }
        }

        public void LoadDictionary(Dictionary<string, List<string>> dictSet)
        {
            List<string> words;
            int wordsTotalSize = 0, codesTotalSize = 0;
            totalWordCount = 0;
            // first calculate the totalsize
            foreach (string key in dictSet.Keys)
            {
                codesTotalSize += key.Length + 1;

                words = dictSet[key];
                totalWordCount += words.Count;
                foreach (string word in words)
                {
                    wordsTotalSize += word.Length + 1;
                }
            }
            IOrderedEnumerable<string> theKeys = dictSet.Keys.OrderBy(x => x);
            totalCodeCount = theKeys.Count();
            
            wordsContent = new char[wordsTotalSize];
            codesContent = new char[codesTotalSize];

            codesObjects = new CodeObject[theKeys.Count()];
            // ok,the codes has been order bye the key (code)
            int codeIndex = 0;

            int wordsStartIndex = 0;
            int codesStartIndex = 0;
            foreach (string key in theKeys)
            {
                codesObjects[codeIndex].CodeStartIndex = codesStartIndex;
                codesObjects[codeIndex].FirstWordStartIndex = wordsStartIndex;

                key.CopyTo(0, codesContent, codesStartIndex, key.Length);
                codesStartIndex += key.Length;
                codesContent[codesStartIndex] = '\0';
                codesStartIndex++;

                List<string> theWords = dictSet[key];
                foreach (string word in theWords)
                {
                    word.CopyTo(0, wordsContent, wordsStartIndex, word.Length);
                    wordsStartIndex += word.Length;
                    wordsContent[wordsStartIndex] = '\0';
                    wordsStartIndex++;
                }
                codeIndex++;
            }            
        }

        public void SaveDictionary(string dictPath)
        {
            try
            {
                //using (BinaryWriter binWriter =
                //    new BinaryWriter(File.Open(dictPath, FileMode.Create)))
                //{
                //    binWriter.Write(totalCodeCount);
                //    binWriter.Write(codesContent.Length);
                //    binWriter.Write(totalWordCount);
                //    binWriter.Write(wordsContent.Length);
                //    // need to write the codeObjss                    
                //    foreach (CodeObject obj in codesObjects)
                //    {
                //        binWriter.Write(obj.CodeStartIndex);
                //        binWriter.Write(obj.FirstWordStartIndex);
                //    }
                //    binWriter.Write(codesContent);
                //    binWriter.Write(wordsContent);
                //    binWriter.Flush();
                //    binWriter.Close();
                //}
                using (BinaryWriter binWriter =
                    new BinaryWriter(File.Open(dictPath, FileMode.Create)))
                {
                    binWriter.Write(totalCodeCount);
                    binWriter.Write(codesContent.Length);
                    binWriter.Write(totalWordCount);
                    binWriter.Write(wordsContent.Length);
                    // need to write the codeObjss
                    foreach (CodeObject obj in codesObjects)
                    {
                        binWriter.Write(obj.CodeStartIndex);
                        binWriter.Write(obj.FirstWordStartIndex);
                    }
                    binWriter.Write(codesContent);
                    binWriter.Write(wordsContent);
                    binWriter.Flush();
                    binWriter.Close();
                }
            }
            catch (IOException e)
            {
                throw (e);
            }
        }

        public void AddWord(string phonetCode, string word)
        {
            /// 两种情况：1、Code已经存在 找到Code对应的单词列表，将该单词加入其中
            /// 2、Code不存在，找到要插入的位置，插入Code，同时要在单词内容中插入单词。
            /// 

            ////////////////////////////////////////////////////////////////////
            int codeStartPos = 0, codeEndPos = totalCodeCount;
            //int codeInsertAfterPos = -1;
            int mid = 0;
            string code;
            bool found = false;
            while (codeStartPos <= codeEndPos)
            {
                mid = (codeStartPos + codeEndPos) / 2;
                code = getContentFrom(codesObjects[mid].CodeStartIndex, codesContent);
                if (code.Equals(phonetCode, StringComparison.OrdinalIgnoreCase))
                {
                    found = true;
                    break;   // only
                }
                if (code.CompareTo(phonetCode) > 0)
                    codeEndPos = mid - 1;
                else
                    codeStartPos = mid + 1;
            }

            if (found)
            {
                int codeIndex = mid;
                CodeObject obj = codesObjects[codeIndex];
                int startPos, endPos;
                startPos = obj.FirstWordStartIndex;

                if ((codeIndex + 1) < totalCodeCount)
                {
                    endPos = codesObjects[codeIndex + 1].FirstWordStartIndex - 1;
                }
                else
                    endPos = totalCodeSize - 1;

                if (endPos != totalWordSize - 1)
                {
                    // 首先，将endPos后面的字符复制到一个临时数组
                    char[] tempWords = new char[totalWordSize - endPos - 1];

                    Array.Copy(wordsContent, endPos + 1, tempWords, 0, totalWordSize - endPos - 1);
                    // 重新设置wordsContent的大小，会自动复制以前的内容到该数组
                    Array.Resize(ref wordsContent, totalWordSize + word.Length + 1);
                    // 将新插入的单词内容复制到wordsContent中
                    Array.Copy(word.ToCharArray(), 0, wordsContent, endPos + 1, word.Length);

                    wordsContent[endPos + word.Length + 1] = '\0';

                    // 将临时数组中的内容复制到新数组
                    Array.Copy(tempWords, 0, wordsContent, endPos + 1 + word.Length + 1, tempWords.Length);

                    totalWordSize += word.Length + 1;  // set the new size of the char array wordsContent 
                    totalWordCount++;
                    tempWords = null;
                }
                else // at the end of the wordscontent
                {
                    Array.Resize(ref wordsContent, totalWordSize + word.Length + 1);
                    Array.Copy(word.ToCharArray(), 0, wordsContent, endPos + 1, word.Length);
                    totalWordSize += word.Length + 1;  // set the new size of the char array wordsContent 
                    totalWordCount++;
                }

                // update all the code objets afther the codeindex....
                for (int i = codeIndex + 1; i < totalCodeCount; i++)
                {
                    codesObjects[i].FirstWordStartIndex += word.Length + 1;
                }
            }
            else
            {
                throw (new Exception("Not implemented!"));
            }

            ////////////////////////////////////////////////////////////////////
            ///// here we should get to find where is the nearest code , and 
            ///// 
            //if (!found)
            //{
            //    if (codeStartPos == codeEndPos)  // 
            //    {
            //        code = getContentFrom(codesObjects[codeStartPos].codeStartIndex, codesContent);
            //        if (code.CompareTo(phonetCode) > 0)  // means i should insert the code before the codeEndPos
            //        {
            //            //codeStartPos = 0;
            //            if (codeEndPos > 0)
            //                codeInsertAfterPos = codeEndPos - 1;
            //        }
            //        else   // means i should insert the code just after the codeEndPos
            //        {
            //            codeInsertAfterPos = codeEndPos;
            //        }
            //    }
            //    else if(codeEndPos < codeStartPos)  //是否？。。。。
            //    {
            //        // fetch the smaller one
            //        //code = getContentFrom(codesObjects[codeEndPos].codeStartIndex, codesContent);
            //        codeInsertAfterPos = codeEndPos;
            //    }

            //    /////////////////// insert the code.
            //    // if the pos is at the end..
            //    if (codeInsertAfterPos >= totalCodeCount - 1)
            //    {
            //        Array.Resize(codesContent, totalCodeSize + phonetCode.Length + 1);
            //        Array.Copy(phonetCode.ToCharArray(), 0, codesContent, totalCodeSize - 1, phonetCode.Length);
            //        codesContent[totalCodeSize + phonetCode.Length] = '\0';

            //        totalCodeSize += phonetCode.Length + 1;
            //        totalCodeCount++;
            //    }
            //        // else insert the code..
            //    else
            //    {
            //        int insertPos = codesObjects[codeInsertAfterPos + 1].codeStartIndex;
            //        char[] temp = new char[totalCodeSize - insertPos];
            //        Array.Copy(codesContent, insertPos, temp, 0, totalCodeSize - insertPos);
            //        Array.Resize(codesContent, totalCodeSize + phonetCode.Length + 1);
            //        Array.Copy(phonetCode.ToCharArray(), 0, codesContent, insertPos, phonetCode.Length);
            //        codesContent[insertPos + phonetCode.Length] = '\0';
            //        Array.Copy(temp, 0, codesContent, insertPos + phonetCode.Length + 1, temp.Length);
            //        totalCodeSize += phonetCode.Length + 1;
            //        totalCodeCount++;


            //    }

            //    // when finished the code... go to insert the word... OH MY GOD..
            //    //
            //    //
            //    // in this section .. the words is new.. OH...You must update the wordsIndex.......... Faint.

            //}
            //else  // found
            //{

            //}
            //////////////////////////////////////////////////////////////////
            //StringBuilder sbWords = new StringBuilder();
            //CodeObject obj;

            //codeIndex = mid;

            //if (found) // found
            //{
            //    obj = codesObjects[codeIndex];
            //    startPos = obj.firstWordStartIndex;

            //    if ((codeIndex + 1) < totalCodeCount)
            //    {
            //        endPos = codesObjects[codeIndex + 1].firstWordStartIndex - 1;
            //    }
            //    else
            //        endPos = totalCodeSize - 1;

            //    totalWordSize += word.Length + 1;  // set the new size of the char array wordsContent 
            //    totalWordCount++;

            //    // 首先，将endPos后面的字符复制到一个临时数组
            //    char[] tempWords = new char[totalWordSize - word.Length - 1 - endPos - 1];
            //    Array.Copy(wordsContent, endPos + 1, tempWords, 0, totalWordSize - word.Length - 1 - endPos - 1);

            //    // 重新设置wordsContent的大小，会自动复制以前的内容到该数组
            //    Array.Resize(wordsContent, totalWordSize);
            //    // 将新插入的单词内容复制到wordsContent中
            //    Array.Copy(word.ToCharArray(), 0, wordsContent, endPos + 1, word.Length);
            //    wordsContent[endPos + word.Length + 1] = '\0';

            //    // 将临时数组中的内容复制到新数组
            //    Array.Copy(tempWords, 0, wordsContent, endPos + word.Length + 1 + 1, tempWords.Length);

            //    tempWords = null;
            //}
            //else
            //{

            //}
        }

        #region Private functions

        private string getContentFrom(int startIndex, char[] content)
        {
            StringBuilder sb = new StringBuilder();
            int start = startIndex;
            if (start < content.Length)
            {
                while (content[start] != '\0')
                {
                    sb.Append(content[start++]);
                }
            }
            return sb.ToString();
        }

        private int GetCodeIndex(string phonetCode)
        {
            int startPos = 0, endPos = totalCodeCount;
            int mid = 0;
            string code;
            bool found = false;
            while (startPos <= endPos)
            {
                mid = (startPos + endPos) / 2;
                code = getContentFrom(codesObjects[mid].CodeStartIndex, codesContent);
                if (code.Equals(phonetCode, StringComparison.OrdinalIgnoreCase))
                {
                    found = true;
                    break;   // only
                }
                if (code.CompareTo(phonetCode) > 0)
                    endPos = mid - 1;
                else
                    startPos = mid + 1;
            }

            if (found)
                return mid;
            else
                return -1;
        }

        [Conditional("TRACE_ON")]
        private void TraceInfo(string title, string info)
        {
            Console.WriteLine(title + ":" + info);
        }

        #endregion
        private bool disposed = false;

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    this.codesContent = null;
                    this.codesObjects = null;
                    codesObjects = null;
                }

                disposed = true;
            }
        }

        ~MemoryDictionary()
        {
            Dispose(false);
        }
    }
}
